import axios, { AxiosError, AxiosInstance, AxiosResponse } from "axios";
import { navigate } from "../actions/Router";
import { NulException, NulExceptionConstants } from "./NulException";
import * as TypeHelper from "./TypeHelper";

interface NulResponse<T> {
  version: number;
  code: NulExceptionConstants;
  message: string;
  result: T;
}

function createAxiosInstance<Response>(withToken: boolean = true) {
  const instance: AxiosInstance = axios.create({
    baseURL: 'http://localhost:8080/'
  });

  if (withToken) {
    instance.interceptors.request.use(config => {
      const token: string | null = localStorage.getItem('token');
      if (token === null) {
        navigate('/login');
      }
      config.headers.Authorization = `Bearer ${token}`;
      return config;
    });
  }

  instance.interceptors.response.use((response: AxiosResponse<NulResponse<Response>>) => {
    return response;
  }, (error: AxiosError<NulResponse<Response>>) => {
    if (error.response) {
      const data: NulResponse<Response> = error.response.data;
      if (data.code === NulExceptionConstants.INVALID_TOKEN) {
        navigate('/login', { invalidToken: true });
      }
      return Promise.reject(new NulException(data.code, data.message));
    } else {
      return Promise.reject(new NulException(NulExceptionConstants.NETWORK_ERROR, error.message));
    }
  });

  return instance;
}

interface PostRequestParams<Params, Request> {
  url: string;
  params?: Params;
  request?: Request;
  withToken?: boolean;
}

function postRequest<Response, Request, Params = null>(params: PostRequestParams<Params, Request>): Promise<AxiosResponse<NulResponse<Response>>> {
  const withToken: boolean = TypeHelper.getValueOrDefault(params.withToken, true);
  const instance: AxiosInstance = createAxiosInstance<Response>(withToken);
  return instance.post<NulResponse<Response>, AxiosResponse<NulResponse<Response>>, Request>(params.url, params.request, {
    params: params.params
  });
}

interface GetRequestParams<Params> {
  url: string;
  params?: Params;
  withToken?: boolean;
}

function getRequest<Response, Params = null>(params: GetRequestParams<Params>): Promise<AxiosResponse<NulResponse<Response>>> {
  const withToken: boolean = TypeHelper.getValueOrDefault(params.withToken, true);
  const instance: AxiosInstance = createAxiosInstance<Response>(withToken);
  return instance.get<NulResponse<Response>>(params.url, {
    params: params.params
  });
}

export { postRequest, getRequest };
export type { NulResponse };